export type AutoReconnectOptions = boolean | {
    maxRetries?: number
    retryInterval?: number
    onMaxRetriesReached?: Function
}

export enum ConnectionStatus {
    Disconnected = 'DISCONNECTED',
    Connected = 'CONNECTED',
    Error = 'ERROR'
}

class SocketService {
    private static instance: SocketService | null = null
    private ws: WebSocket | null = null
    private listeners: Record<string, Function[]> = {}
    private autoReconnect: AutoReconnectOptions = true
    private retries: number = 0
    private connectionStatus: ConnectionStatus = ConnectionStatus.Disconnected

    private constructor() {
        this.connect()
    }

    public static getInstance(): SocketService {
        if (!SocketService.instance) {
            SocketService.instance = new SocketService()
        }
        return SocketService.instance
    }

    public setAutoReconnectOptions(options: AutoReconnectOptions) {
        this.autoReconnect = options
    }

    public connect() {
        this.ws = new WebSocket('ws://localhost:18001/webSocket')
        this.ws.onopen = () => {
            this.connectionStatus = ConnectionStatus.Connected
            this.emit('connected', null)
        }
        this.ws.onerror = () => {
            this.connectionStatus = ConnectionStatus.Error
            this.emit('error', null)
        }
        this.ws.onclose = () => {
            this.connectionStatus = ConnectionStatus.Disconnected
            this.emit('disconnected', null)
            if (this.shouldReconnect()) {
                setTimeout(() => this.connect(), this.getRetryInterval())
            }
        }
        this.ws.onmessage = (event) => {
            this.emit('message', JSON.parse(event.data))
        }
    }

    private shouldReconnect(): boolean {
        if (typeof this.autoReconnect === 'boolean') {
            return this.autoReconnect
        } else if (this.autoReconnect) {
            const { maxRetries } = this.autoReconnect

            if (maxRetries !== undefined) {
                if (this.retries < maxRetries) {
                    this.retries++
                    return true
                } else if (this.retries >= maxRetries) {
                    this.autoReconnect.onMaxRetriesReached && this.autoReconnect.onMaxRetriesReached()
                    return false
                }
            }
        }
        return false
    }

    private getRetryInterval(): number {
        if (typeof this.autoReconnect === 'boolean') {
            return 1000
        } else if (this.autoReconnect && this.autoReconnect.retryInterval) {
            return this.autoReconnect.retryInterval
        }
        return 1000
    }

    public send(data: any) {
        if (this.ws && this.ws.readyState === WebSocket.OPEN) {
            this.ws.send(JSON.stringify(data))
        } else {
            console.error('WebSocket 连接未打开')
        }
    }

    public close() {
        if (this.ws) {
            this.ws.close()
            SocketService.instance = null
        }
    }

    private emit(event: string, data: any) {
        if (!this.listeners[event]) {
            return
        }
        this.listeners[event].forEach((listener) => listener(data))
    }

    public on(event: string, listener: Function) {
        if (!this.listeners[event]) {
            this.listeners[event] = []
        }
        this.listeners[event].push(listener)
        if (event === 'connected' && this.connectionStatus === ConnectionStatus.Connected) {
            listener()
        }
    }

    public off(event: string, listener: Function) {
        if (!this.listeners[event]) {
            return
        }
        this.listeners[event] = this.listeners[event].filter((l) => l !== listener)
    }

    public getConnectionStatus(): ConnectionStatus {
        return this.connectionStatus
    }

    public watchConnectionStatus(callback: (status: ConnectionStatus) => void) {
        this.on('connected', () => callback(ConnectionStatus.Connected))
        this.on('disconnected', () => callback(ConnectionStatus.Disconnected))
        this.on('error', () => callback(ConnectionStatus.Error))
    }
}

export default SocketService
